<?php

namespace yiyus\yiyusaddons\addons;


use GuzzleHttp\Client;
use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\TransferStats;
use PhpZip\Exception\ZipException;
use PhpZip\ZipFile;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Component\VarExporter\VarExporter;
use think\Cache;
use think\Db;
use think\Exception;
use think\Log;


class Service
{
    /**
     * 插件列表
     */
    public static function addons($params = [])
    {
        $params['domain'] = request()->host(true);
        return self::sendRequest('/addon/index', $params, 'GET');
    }

    /**
     * 远程下载插件
     *
     * @param array $params 扩展参数
     * @return  string
     */
    public static function download($params)
    {
        $addonsTempDir = self::getAddonsBackupDir();
        $tmpFile = $addonsTempDir . $params['addon_name'] . ".zip";
        try {
            $client = self::getClient();
            $response = $client->post('/addon/download/getPlugIn', ['query' => $params]);
            $body = $response->getBody();
            $content = $body->getContents();
            if (substr($content, 0, 1) === '{') {
                $json = (array)json_decode($content, true);
                //如果传回的是一个下载链接,则再次下载
                if ($json['data'] && isset($json['data']['url'])) {
                    $response = $client->get($json['data']['url']);
                    $body = $response->getBody();
                    $content = $body->getContents();
                } else {
                    //下载返回错误，抛出异常
                    throw new AddonException($json['msg'], $json['code'], $json['data']);
                }
            }

        } catch (TransferException $e) {
            throw new Exception("Addon package download failed");
        }
        if ($write = fopen($tmpFile, 'w')) {
            fwrite($write, $content);
            fclose($write);
            return $tmpFile;
        }
        throw new Exception("No permission to write temporary files");
    }


    /**
     * 解压插件
     *
     * @param string $name 插件名称
     * @return  string
     * @throws  Exception
     */
    public static function unzip($name)
    {
        if (!$name) {
            throw new Exception('Invalid parameters');
        }
        $addonsBackupDir = self::getAddonsBackupDir();
        $file = $addonsBackupDir . $name . '.zip';

        // 打开插件压缩包
        $zip = new ZipFile();
        try {
            $zip->openFile($file);
        } catch (ZipException $e) {
            $zip->close();
            throw new Exception('Unable to open the zip file');
        }

        $dir = self::getAddonDir($name);
        if (!is_dir($dir)) {
            @mkdir($dir, 0755);
        }

        // 解压插件压缩包
        try {
            $zip->extractTo($dir);
        } catch (ZipException $e) {
            throw new Exception('Unable to extract the file');
        } finally {
            $zip->close();
        }
        return $dir;
    }


    /**
     * 备份插件
     * @param string $name 插件名称
     * @return bool
     * @throws Exception
     */
    public static function backup($name)
    {
        $addonsBackupDir = self::getAddonsBackupDir();
        $file = $addonsBackupDir . $name . '-backup-' . date("YmdHis") . '.zip';
        $zipFile = new ZipFile();
        try {
            $zipFile
                ->addDirRecursive(self::getAddonDir($name))
                ->saveAsFile($file)
                ->close();
        } catch (ZipException $e) {

        } finally {
            $zipFile->close();
        }

        return true;
    }

    /**
     * 检测插件是否完整
     *
     * @param string $name 插件名称
     * @return  boolean
     * @throws  Exception
     */
    public static function check($name)
    {
        if (!$name || !is_dir(ADDON_PATH . $name)) {
            throw new Exception('Addon not exists');
        }
        $addonClass = get_addon_class($name);
        if (!$addonClass) {
            throw new Exception("The addon file does not exist");
        }
        $addon = new $addonClass();
        if (!$addon->checkInfo()) {
            throw new Exception("The configuration file content is incorrect");
        }
        return true;
    }


    /**
     * 导入SQL
     *
     * @param string $name 插件名称
     * @param string $fileName SQL文件名称
     * @return  boolean
     */
    public static function importsql($name, $fileName = null)
    {
        $fileName = is_null($fileName) ? 'install.sql' : $fileName;
        $sqlFile = self::getAddonDir($name) . $fileName;
        if (is_file($sqlFile)) {
            $lines = file($sqlFile);
            $templine = '';
            foreach ($lines as $line) {
                if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') {
                    continue;
                }

                $templine .= $line;
                if (substr(trim($line), -1, 1) == ';') {
                    $templine = str_ireplace('__PREFIX__', config('database.prefix'), $templine);
                    $templine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $templine);
                    try {
                        Db::getPdo()->exec($templine);
                    } catch (\PDOException $e) {
                        //$e->getMessage();
                    }
                    $templine = '';
                }
            }
        }
        return true;
    }

    /**
     * 安装插件
     *
     * @param string $name 插件名称
     * @param boolean $force 是否覆盖
     * @param array $extend 扩展参数
     * @return  boolean
     * @throws  Exception
     * @throws  AddonException
     */
    public static function install($name, $extend = [])
    {
        if (!$name || (is_dir(ADDON_PATH . $name))) {
            throw new Exception('Addon already exists');
        }
        $extend['domain'] = request()->host(true);
        $extend['addon_name'] = $name;
        // 远程下载插件
        $tmpFile = Service::download($extend);
        $addonDir = self::getAddonDir($name);
        try {
            // 解压插件压缩包到插件目录
            Service::unzip($name);
            // 检查插件是否完整
            Service::check($name);
        } catch (AddonException $e) {
            @rmdirs($addonDir);
            throw new AddonException($e->getMessage(), $e->getCode(), $e->getData());
        } catch (Exception $e) {
            @rmdirs($addonDir);
            throw new Exception($e->getMessage());
        } finally {
            // 移除临时文件
            @unlink($tmpFile);
        }

        Db::startTrans();
        try {
            // 执行安装脚本
            $class = get_addon_class($name);
            if (!class_exists($class)) throw new \Exception("Class File $name not exist");
            $addon = new $class();
            $addon->install();
            Db::commit();
            return true;
        } catch (Exception $e) {
            @rmdirs($addonDir);
            Db::rollback();
            throw new Exception($e->getMessage());
        }

    }

    /**
     * 卸载插件
     *
     * @param string $name
     * @return  boolean
     * @throws  Exception
     */
    public static function uninstall($name)
    {
        if (!$name || !is_dir(ADDON_PATH . $name)) {
            throw new Exception('Addon not exists');
        }
        // 执行卸载脚本
        try {
            $class = get_addon_class($name);
            if (class_exists($class)) {
                $addon = new $class();
                $addon->uninstall();
            }
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
        // 移除插件目录
        rmdirs(ADDON_PATH . $name);
        return true;
    }

    /**
     * 获取指定插件的目录
     */
    public static function getAddonDir($name)
    {
        $dir = ADDON_PATH . $name . DS;
        return $dir;
    }

    /**
     * 获取插件备份目录
     */
    public static function getAddonsBackupDir()
    {
        $dir = RUNTIME_PATH . 'addons' . DS;
        if (!is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        return $dir;
    }

    /**
     * 获取请求对象
     * @return Client
     */
    public static function getClient()
    {
        $options = [
            'base_uri' => "http://tpaf.com",
            'timeout' => 30,
            'connect_timeout' => 30,
            'verify' => false,
            'http_errors' => false,
            'headers' => [
                'X-REQUESTED-WITH' => 'XMLHttpRequest',
                'Referer' => dirname(request()->root(true)),
                'User-Agent' => 'FastAddon',
            ]
        ];
        static $client;
        if (empty($client)) {
            $client = new Client($options);
        }
        return $client;
    }

    /**
     * 发送请求
     * @return array
     * @throws Exception
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public static function sendRequest($url, $params = [], $method = 'POST')
    {
        $json = [];
        try {
            $client = self::getClient();
            $options = strtoupper($method) == 'POST' ? ['form_params' => $params] : ['query' => $params];
            $response = $client->request($method, $url, $options);
            $body = $response->getBody();
            $content = $body->getContents();
            $json = (array)json_decode($content, true);
        } catch (TransferException $e) {
            throw new Exception(__('Network error'));
        } catch (\Exception $e) {
            throw new Exception(__('Unknown data format'));
        }
        return $json;
    }

    /**
     * 初始化插件菜单
     * @param array $menus
     * @return bool
     * @throws \Exception
     */
    private static function addMenu(array $menus)
    {
        list($pid, $childs) = self::insetMenu($menus, self::getAddonMenuID());
        if ($childs) {
            foreach ($childs as $val) {
                list($pid2, $childs2) = self::insetMenu($val, $pid);
                if ($childs2) {
                    foreach ($childs2 as $v) {
                        self::insetMenu($v, $pid2);
                    }
                }
            }
        }
        return true;
    }

    /**
     * 执行插件菜单写入
     * @param $menu
     * @return array
     * @throws \Exception
     */
    private static function insetMenu($menu, $pid)
    {
        $menu['pid'] = $pid;
        $childs = [];
        if(!empty($menu['child'])){
            $childs = $menu['child'];
            unset($menu['child']);
        }
        $pid = Db::table('sys_module')->insertGetId($menu);
        if (!$pid) throw new \Exception("初始化插件菜单异常");
        return [$pid, $childs];
    }

    /**
     * 获取插件管理菜单ID
     * @return int|mixed|string
     * @throws \Exception
     */
    private static function getAddonMenuID()
    {
        $admin_module_name = config('addons.admin_temp_name');
        $addonMenuID = Db::table('sys_module')->where(['module' => $admin_module_name, 'controller' => 'Addon'])->column('module_id');
        if (empty($addonMenuID)) {
            $addon_id = Db::table('sys_module')->insertGetId([
                'module_name' => '插件管理',  //模块标题
                'module' => $admin_module_name,  //项目名
                'controller' => 'Addon',  //控制器名
                'method' => 'list',  //方法名
                'pid' => 0,  //上级模块ID
                'level' => 1, //深度等级
                'url' => $admin_module_name . '/Addon/list',  //链接地址
                'is_menu' => 1,  //是否是菜单
                'sort' => 1,  //排序（同级有效）
                'desc' => '插件管理',  //模块描述
            ]);
            if (!$addon_id) throw new \Exception("服务器内部异常，插件安装失败");
        } else {
            $addon_id = $addonMenuID[0];
        }
        return $addon_id;
    }

    /**
     * 初始化配置与菜单
     * @param String $addon_name 插件名称
     * @return bool
     * @throws \Exception
     */
    public static function initAddon($addon_name)
    {
        $config = (include ADDON_PATH . '/' . $addon_name . '/config.php');
        $addons = (include ROOT_PATH . '/application/extra/addons.php');
        if (!empty($config['init']['menu'])) self::addMenu($config['init']['menu']);
        self::importsql($addon_name);
        $change = false;
        if (!empty($config['init']['hooks'])) {
            $addons['hooks'] = array_merge($addons['hooks'], $config['init']['hooks']);
            $change = true;
        }
        if (!empty($config['init']['route'])) {
            $addons['route'] = array_merge($addons['route'], $config['init']['route']);
            $change = true;
        }
        if ($change) set_addon_config(ROOT_PATH . '/application/extra/addons.php', $addons);
        return true;
    }

    /**
     * 移除插件配置
     * @param $addon_name
     * @return bool
     * @throws \Exception
     */
    public static function removeAddon($addon_name)
    {
        $addons = (include ROOT_PATH . '/application/extra/addons.php');
        $config = (include ADDON_PATH . '/' . $addon_name . '/config.php');
        if (!empty($config['init']['menu'])) self::delMenu($addon_name);
        $change = false;
        if (!empty($config['init']['hooks'])) {
            foreach ($config['init']['hooks'] as $k => $v) {
                unset($addons['hooks'][$k]);
            }
            $change = true;
        }
        if (!empty($config['init']['route'])) {
            foreach ($config['init']['route'] as $k => $v) {
                unset($addons['route'][$k]);
            }
            $change = true;
        }
        if ($change) set_addon_config(ROOT_PATH . '/application/extra/addons.php', $addons);
        return true;
    }

    /**
     * 移除插件菜单
     * @param $addon_name
     * @return bool
     * @throws Exception
     * @throws \think\exception\PDOException
     */
    private static function delMenu($addon_name)
    {
       $rst = Db::table('sys_module')
           ->where(['url' => ['module_name' => $addon_name,'like','%addons/'.$addon_name.'%']])
           ->delete();
       if(!$rst)throw new \Exception("移除插件菜单异常");
       return true;
    }
}